//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.DataLayer
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Controls;

    public static class ExternalRegistryHelperProxy
    {
        /// <summary>
        /// The registry connection.
        /// </summary>
        private static Connection registryConn;

        /// <summary>
        /// Stores the loaded provider helpers.
        /// </summary>
        private static Dictionary<Guid, ExternalRegistryHelper> loadedProviderHelpers = new Dictionary<Guid, ExternalRegistryHelper>();

        /// <summary>
        /// Initializes the specified connection.
        /// </summary>
        /// <param name="connection">The connection.</param>
        public static void Initialize(Connection connection)
        {
            registryConn = connection;
        }

        /// <summary>
        /// Clears the provider.
        /// </summary>
        /// <param name="providerId">The provider id.</param>
        public static void ClearProvider(Guid providerId)
        {
            if (loadedProviderHelpers.ContainsKey(providerId))
            {
                loadedProviderHelpers.Remove(providerId);
            }
        }

        /// <summary>
        /// Creates the instance.
        /// </summary>
        /// <param name="providerId">The provider id.</param>
        public static void CreateInstance(Guid providerId)
        {
            if (!loadedProviderHelpers.ContainsKey(providerId))
            {
                LoadProvider(providerId);
            }

            if (loadedProviderHelpers.ContainsKey(providerId))
            {
                ExternalRegistryHelper externalRegistryHelper = loadedProviderHelpers[providerId];
                externalRegistryHelper.CreateInstance();
            }
        }

        /// <summary>
        /// Initializes the configurator.
        /// </summary>
        /// <param name="providerId">The provider id.</param>
        public static void InitializeConfigurator(Guid providerId)
        {
            if (loadedProviderHelpers.ContainsKey(providerId))
            {
                ExternalRegistryHelper externalRegistryHelper = loadedProviderHelpers[providerId];
                externalRegistryHelper.InitializeConfigurator();
            }
        }


        /// <summary>
        /// Retrieves the specified object.
        /// </summary>
        /// <param name="store">The store.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static DataObject Retrieve(DataReferenceStore store, string key)
        {
            Guid providerID = ((IObject)store.Provider).ID;

            CreateInstance(providerID);

            if (loadedProviderHelpers.ContainsKey(providerID))
            {
                return loadedProviderHelpers[providerID].Retrieve(store.DataProviderParameters, key);
            }
            else
            {
                throw new DataProviderException("Can not load data provider with the name " + store.Provider.Name);
            }
        }

        /// <summary>
        /// Stores the specified dataObject.
        /// </summary>
        /// <param name="referenceStore">The reference store.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Store(DataReferenceStore referenceStore, DataObject value)
        {
            Guid providerID = (referenceStore.Provider as IObject).ID;

            CreateInstance(providerID);

            if (loadedProviderHelpers.ContainsKey(providerID))
            {
                return loadedProviderHelpers[providerID].Store(referenceStore.DataProviderParameters, value);
            }
            else
            {
                throw new DataProviderException("Can not load data provider with the name " + referenceStore.Provider.Name);
            }
        }

        /// <summary>
        /// Stores the specified dataObject.
        /// </summary>
        /// <param name="referenceStore">The reference store.</param>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static string Store(DataReferenceStore referenceStore, DataObject value, string key)
        {
            Guid providerID = (referenceStore.Provider as IObject).ID;

            CreateInstance(providerID);

            if (loadedProviderHelpers.ContainsKey(providerID))
            {
                return loadedProviderHelpers[providerID].Store(referenceStore.DataProviderParameters, key, value);
            }
            else
            {
                throw new DataProviderException("Can not load data provider with the name " + referenceStore.Provider.Name);
            }
        }

        /// <summary>
        /// Gets the parameters.
        /// </summary>
        /// <param name="providerId">The provider id.</param>
        /// <returns></returns>
        public static string GetParameters(Guid providerId)
        {
            if (loadedProviderHelpers.ContainsKey(providerId))
            {
                return loadedProviderHelpers[providerId].GetParameters();
            }
            else
            {
                throw new DataProviderException("Provider is not loaded");
            }
        }

        /// <summary>
        /// Sets the parameters.
        /// </summary>
        /// <param name="providerId">The provider id.</param>
        /// <param name="parameters">The parameters.</param>
        public static void SetParameters(Guid providerId, string parameters)
        {
            if (loadedProviderHelpers.ContainsKey(providerId))
            {
                loadedProviderHelpers[providerId].SetParameters(parameters);
            }
            else
            {
                throw new DataProviderException("Provider is not loaded");
            }
        }

        /// <summary>
        /// Loads the provider.
        /// </summary>
        /// <param name="providerID">The provider ID.</param>
        private static void LoadProvider(Guid providerID)
        {
            ExternalRegistryHelper externalRegistryHelper = null;
            AppDomain childAppdomain = null;
            try
            {
                childAppdomain = ExternalRegistryHelperProxy.CreateAppDomain(providerID.ToString());
                
                externalRegistryHelper = childAppdomain.CreateInstanceAndUnwrap
                    (typeof(ExternalRegistryHelper).Assembly.FullName,
                    typeof(ExternalRegistryHelper).FullName) as ExternalRegistryHelper;

                externalRegistryHelper.Initialize(registryConn, providerID);

                loadedProviderHelpers[providerID] = externalRegistryHelper;
            }
            catch (Exception exp)
            {
                throw new DataProviderException(exp.Message, exp);
            }
        }

        /// <summary>
        /// Cleans up.
        /// </summary>
        /// <param name="providerID">The provider ID.</param>
        public static void CleanUp()
        {
            if (loadedProviderHelpers != null)
            {
                foreach (ExternalRegistryHelper helper in loadedProviderHelpers.Values)
                {
                    try
                    {
                        string tempPath = helper.TempPath;

                        UnloadAppDomain(helper.GetCurrentAppDomain);

                        System.IO.Directory.Delete(tempPath, true);
                    }
                    catch (Exception)
                    {
                        // Ignore exception: Suppose the provider is already downloaded. 
                    }
                }
            }

            loadedProviderHelpers = new Dictionary<Guid, ExternalRegistryHelper>();
        }

        #region private methods
        /// <summary>
        /// Creates a new app domain.
        /// </summary>
        /// <returns>New instance of the app domain.</returns>
        private static AppDomain CreateAppDomain(string appDomainName)
        {
            AppDomain newDomain = AppDomain.CreateDomain(
                appDomainName, 
                AppDomain.CurrentDomain.Evidence,
                AppDomain.CurrentDomain.BaseDirectory,
                AppDomain.CurrentDomain.RelativeSearchPath, false);
            return newDomain;
        }

        /// <summary>
        /// Unloads the executor app domain.
        /// </summary>
        /// <param name="executorDomain">Instance of the AppDomain.</param>
        private static void UnloadAppDomain(AppDomain appDomain)
        {
            if (appDomain != null)
            {
                AppDomain.Unload(appDomain);

                // This is make sure that the appdomain is completly Unloaded.
                System.Threading.Thread.Sleep(500);
            }
        }
        #endregion
    }
}
